﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using FireStarter.Base;
using FireStarter.Base.BusinessObjectMerger;
using FireStarterModeller.Logic;
using FireStarterModeller.Logic.FsStartLoading;
using Habanero.Base;
using Habanero.BO;
using NUnit.Framework;

namespace FireStarterModeller.Test.Logic
{
    public abstract class TestFsStartLoadingBase
    {
        private DataStoreInMemory _dataStoreInMemory;

        protected TestFsStartLoadingBase()
        {
            ExpectedDatastoreElementCount = 1;
        }

        [TestFixtureSetUp]
        public void SetupFixture()
        {
            TestUtilsLogic.SetupFixture();

            if (Directory.Exists(GetFsStartLoadingTempDirectory())) return;
            
            Directory.CreateDirectory(GetFsStartLoadingTempDirectory());
        }

        protected int ExpectedDatastoreElementCount { get; set; }

        [SetUp]
        public void SetupTest()
        {
            TestUtilsLogic.SetupTest();
        }

        [TearDown]
        public void TearDownTest()
        {
            RemoveFile(GetFstartFileName());
        }



        // ReSharper disable InconsistentNaming
        /// <summary>
        /// This test should always be ignored unless there has been a change to one of the objects and you wish to recreate the .fstart file to add to the resourcesfortesting.resx
        /// </summary>
        [Ignore("This method can be used to create a new .fsart after a change to one of the schema objects")] //TODO Andrew Russell 28 Oct 2010: Ignored Test - This method can be used to create a new .fsart after a change to one of the schema objects
        [Test]
        public void Test_MergableBO_ReadsFromCreated_fstart_ShouldBeSame_ForFileCreationOnly()
        {
            //---------------Set up test pack-------------------
            ResetNonValidatingDataAccessorInMemory();
            var mergableBO = GetStandardMergableBO();
            SaveFstartToFile(GetFstartFileName());
            //---------------Assert Precondition----------------

            //---------------Execute Test ----------------------
            const int datastoreElementCountAssert = 1;
            var mergableBoFromFile = GetStandardBoFromFile(mergableBO, ExpectedDatastoreElementCount, GetFstartFileName());
            //---------------Test Result -----------------------
            var expectedMergableBO = GetStandardMergableBO();
            var areEqual = MergeCompareBusinessObjects(expectedMergableBO, mergableBoFromFile);
            Assert.IsTrue(areEqual, "The MergableBOs are not the same");
        }

        // ReSharper disable InconsistentNaming
        /// <summary>
        /// The .fstart files created by this test will be created in a temp directory under the fstartloading directory.
        /// The temp directory and all .fstart files in the temp directory will be removed after all the tests in this fixture have run.
        /// The purpose of this test is to ensure that the Comparing of the two identical fstarts is working correctly.
        /// </summary>
        [Test]
        public void Test_MergableBO_ReadsFromCreated_fstart_ShouldBeSame()
        {
            //---------------Set up test pack-------------------
            ResetNonValidatingDataAccessorInMemory();
            var mergableBO = GetStandardMergableBO();
            SaveFstartToFileInTempDirectory(GetFstartFileName());
            //---------------Assert Precondition----------------

            //---------------Execute Test ----------------------
            //const int datastoreElementCountAssert = 1;
            var mergableBoFromFile = GetStandardBoFromFileInTempDirectory(mergableBO, ExpectedDatastoreElementCount, GetFstartFileName());
            //---------------Test Result -----------------------
            var expectedMergableBO = GetStandardMergableBO();
            var areEqual = MergeCompareBusinessObjects(expectedMergableBO, mergableBoFromFile);
            Assert.IsTrue(areEqual, "The MergableBOs are not the same");
        }
        /// <summary>
        /// Loads a standard fstart and compares it to a standard set of BOs to ensure that the fstart loading is working correctly.
        /// </summary>
        [Test]
        public void Test_MergableBO_ReadsFromStandard_fstart_ShouldBeSame()
        {
            //---------------Set up test pack-------------------
            ResetNonValidatingDataAccessorInMemory();
            //---------------Assert Precondition----------------

            //---------------Execute Test ----------------------
            //const int datastoreElementCountAssert = 1;
            var mergableBoFromFile = GetDefaultStandardBoFromResourceFile(ExpectedDatastoreElementCount, GetFstartFileName());
            //---------------Test Result -----------------------
            var expectedMergbableBO = GetStandardMergableBO();
            var mergeBO = MergeableBORegistry.Instance.Resolve(expectedMergbableBO.ClassDef.ClassType);
            var result = mergeBO.CompareBusinessObjects(expectedMergbableBO, mergableBoFromFile);
            Assert.IsTrue(result.AreEqual, "The MergableBOs are not the same : " + result.CompareFailures());
        }

        // ReSharper restore InconsistentNaming


        protected abstract IMergableBo GetStandardMergableBO();

        protected abstract string GetFstartFileName();

        private void ResetNonValidatingDataAccessorInMemory()
        {
            _dataStoreInMemory = new DataStoreInMemory();
            BORegistry.DataAccessor = new NonValidatingDataAccessorInMemory(_dataStoreInMemory);
        }

        private void SaveFstartToFile(string fileName)
        {
            var fsStartLoader = new FsStartLoader();
            fsStartLoader.SaveDataStoreToFile(_dataStoreInMemory, GetFsStartLoadingDirectory() + fileName);
        }

        private void SaveFstartToFileInTempDirectory(string fileName)
        {
            var fsStartLoader = new FsStartLoader();
            fsStartLoader.SaveDataStoreToFile(_dataStoreInMemory, GetFsStartLoadingTempDirectory() + fileName);
        }

        private T GetStandardBoFromFile<T>(T businessObject, int datastoreElementCountAssert, string fileName) where T : class, IBusinessObject
        {
            return GetStandardBoFromFile(businessObject, datastoreElementCountAssert, fileName, false);
        }

        private T GetStandardBoFromFileInTempDirectory<T>(T businessObject, int datastoreElementCountAssert, string fileName) where T : class, IBusinessObject
        {
            return GetStandardBoFromFile(businessObject, datastoreElementCountAssert, fileName, true);
        }

        private T GetStandardBoFromFile<T>(T businessObject, int datastoreElementCountAssert, string fileName, bool useTempDirectory = true) where T : class, IBusinessObject
        {
            var fsStartLoader = new FsStartLoader();
            var directory = GetFsStartLoadingDirectory();
            if (useTempDirectory)
            {
                directory = GetFsStartLoadingTempDirectory();
            }
            var loadDataStoreFromFile = fsStartLoader.LoadDataStoreFromFile(directory + fileName);
            Assert.AreEqual(datastoreElementCountAssert, loadDataStoreFromFile.Count);
            ResetNonValidatingDataAccessorInMemory();
            _dataStoreInMemory.AllObjects = loadDataStoreFromFile;
            var returnedBo = _dataStoreInMemory.Find<T>(businessObject.ID);

            return returnedBo;
        }

        private static IMergableBo GetDefaultStandardBoFromResourceFile(int datastoreElementCountAssert, string fileName) 
        {
            MemoryStream memoryStream = null;

            var fileNameWithoutExtension = GetFileNameWithoutExtension(fileName);

            var bytes = (byte[])ResourcesForTesting.ResourceManager.GetObject(fileNameWithoutExtension, ResourcesForTesting.Culture);
            if (bytes != null) memoryStream = new MemoryStream(bytes);


            var dataStoreFromResourceFile = FsStartLoaderSpy.CallLoadDataStore(memoryStream);
            Assert.AreEqual(datastoreElementCountAssert, dataStoreFromResourceFile.Count);

            var returnedBo = dataStoreFromResourceFile.FirstOrDefault().Value as IMergableBo;
            return returnedBo;
        }


        private static string GetFileNameWithoutExtension(string fileName)
        {
            return fileName.Substring(0, fileName.IndexOf("."));
        }

        private static bool MergeCompareBusinessObjects<TMergableBO>(TMergableBO x, TMergableBO y) where TMergableBO : class, IMergableBo
        {
            var mergeBO = MergeableBORegistry.Instance.Resolve<TMergableBO>();
            return mergeBO.CompareBusinessObjects(x, y).AreEqual;
        }

        private static void RemoveFile(string fileName)
        {

            FsStartTestindDirectory.RemoveFile(fileName);

/*            if (File.Exists(GetFsStartLoadingTempDirectory() + fileName))
            {
                File.Delete(GetFsStartLoadingTempDirectory() + fileName);
            }*/
        }
        private static string GetFsStartLoadingDirectory()
        {
            return FsStartTestindDirectory.GetFsStartLoadingDirectory();
        }


        private static string GetFsStartLoadingTempDirectory()
        {
            return FsStartTestindDirectory.GetFsStartLoadingTempDirectory();
        }
    }

    internal class FsStartTestindDirectory
    {
        internal static void RemoveFile(string fileName)
        {
            if (File.Exists(GetFsStartLoadingTempDirectory() + fileName))
            {
                File.Delete(GetFsStartLoadingTempDirectory() + fileName);
            }
        }


        #region Get Directory

        private static string AssemblyDirectory
        {
            get
            {
                string codeBase = Assembly.GetExecutingAssembly().CodeBase;
                UriBuilder uri = new UriBuilder(codeBase);
                string path = Uri.UnescapeDataString(uri.Path);
                return Path.GetDirectoryName(path);
            }
        }

        private static string GetProjectDirectory()
        {
            return AssemblyDirectory.Substring(0, AssemblyDirectory.IndexOf("bin"));
        }


        internal static string GetFsStartLoadingDirectory()
        {
            return GetFsStartLoadingDirectory(false);
        }

        internal static string GetFsStartLoadingTempDirectory()
        {
            return GetFsStartLoadingDirectory(true);
        }

        private static string GetFsStartLoadingDirectory(bool useTempDirectory = true)
        {
            var directory = GetProjectDirectory() + @"FsStartLoading\";
            if (useTempDirectory) directory += @"temp\";

            return directory;
        }

        #endregion
    }

    internal class FsStartLoaderSpy : FsStartLoader
    {
        public static Dictionary<Guid, IBusinessObject> CallLoadDataStore(Stream stream)
        {
            return LoadDataStore(stream);
        }
    }

}
